package common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import common.constant.HttpStatus;
import common.exception.RRException;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信公众号 网页开发 获取用户微信信息
 * 公众平台以access_token为接口调用凭据，来调用接口，所有接口的调用需要先获取access_token，access_token在2小时内有效，过期需要重新获取，但1天内获取次数有限，开发者需自行存储
 * 1 第一步：用户同意授权，获取code (前端处理 返回给我们后端)
 * 2 第二步：通过code换取网页授权access_token
 * 3 第三步：刷新access_token（如果需要）
 * 4 第四步：拉取用户信息(需scope为 snsapi_userinfo)
 * 5 附：检验授权凭证（access_token）是否有效
 */
@Component
public class WXUtils {

    /**
     * 微信小程序 通过 appid appsecret 以及前端传的code 获取用户openid session_key
     * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
     */
    public static Map<String, Object> getMiniProgramUserOpenId(String code, String appId, String secret) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code";
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject(url, String.class);
        System.out.println("微信小程序==result===" + result);
        JSONObject jsonObject = JSON.parseObject(result, JSONObject.class);
        if (jsonObject != null) {
            Map<String, Object> map = new HashMap<>(16);
            if (jsonObject.containsKey("errcode")) {
                System.out.println(jsonObject.get("errmsg").toString());
                if ("-1".equals(jsonObject.get("errcode").toString())) {
                    throw new RRException("系统繁忙，请稍后再试");
                } else if ("40029".equals(jsonObject.get("errcode").toString())) {
                    throw new RRException("code无效");
                } else if ("45011".equals(jsonObject.get("errcode").toString())) {
                    throw new RRException("请求频繁,请稍后再试");
                } else if ("40163".equals(jsonObject.get("errcode").toString())) {
                    throw new RRException("code已被使用");
                } else if ("0".equals(jsonObject.get("errcode").toString())) {
                    map.put("openid", jsonObject.get("openid"));
                    map.put("sessionKey", jsonObject.get("session_key"));
                    return map;
                } else {
                    throw new RRException("系统繁忙，请稍后再试");
                }
            } else {
                map.put("openid", jsonObject.get("openid"));
                map.put("sessionKey", jsonObject.get("session_key"));
                return map;
            }
        } else {
            throw new RRException("微信小程序获取用户信息异常");
        }
    }


    /**
     * 微信公众号 网页开发 通过 code 获取 access_token
     * 参数	是否必须	 说明
     * appid	是	公众号的唯一标识
     * secret	是	公众号的appsecret
     * code	是	微信用户授权后的code参数
     * grant_type	是	填写为authorization_code
     * <p>
     * 返回成功：access_token	网页授权接口调用凭证,注意：此access_token与基础支持的access_token不同
     * expires_in	access_token接口调用凭证超时时间，单位（秒）
     * refresh_token	用户刷新access_token
     * openid	用户唯一标识，请注意，在未关注公众号时，用户访问公众号的网页，也会产生一个用户和公众号唯一的OpenID
     * scope	用户授权的作用域，使用逗号（,）分隔
     * <p>
     * 返回失败："errcode":40029,"errmsg":"invalid code"
     * https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#2
     */
    public static Map<String, Object> getOffiAccountAccessTokenByCode(String code, String appId, String secret) {
        if (StringUtils.isBlank(code) || StringUtils.isBlank(appId) || StringUtils.isBlank(secret)) {
            throw new RRException("参数异常", HttpStatus.ERROR);
        }
        RestTemplate restTemplate = new RestTemplate();
        String accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code";
        String accessTokenResult = restTemplate.getForObject(accessTokenUrl, String.class);
        JSONObject jsonObject = JSON.parseObject(accessTokenResult, JSONObject.class);
        System.out.println("通过 code 获取 access_token====" + jsonObject);
        if (jsonObject != null) {
            if (!jsonObject.containsKey("errcode")) {
                Map<String, Object> map = new HashMap<>(16);
                map.put("openid", jsonObject.get("openid").toString());
                map.put("accessToken", jsonObject.get("access_token").toString());
                //access_token接口调用凭证超时时间，单位（秒）默认7200秒
                map.put("expiresIn", jsonObject.get("expires_in").toString());
                map.put("refreshToken", jsonObject.get("refresh_token").toString());
                map.put("scope", jsonObject.get("scope").toString());
                return map;
            } else {
                throw new RRException(jsonObject.get("errmsg").toString(), HttpStatus.ERROR);
            }
        } else {
            throw new RRException("获取access_token请求异常", HttpStatus.ERROR);
        }
    }

    /**
     * 微信 公众号 网页开发
     * 验证accessToken 是否过期 true没有过期,false 已经过期
     */
    public boolean checkAccessToken(String accessToken, String openid) {
        if (StringUtils.isBlank(accessToken) || StringUtils.isBlank(openid)) {
            throw new RRException("参数异常", HttpStatus.ERROR);
        }
        RestTemplate restTemplate = new RestTemplate();
        String accessTokenUrl = "https://api.weixin.qq.com/sns/auth?access_token=" + accessToken + "&openid=" + openid;
        String accessTokenResult = restTemplate.getForObject(accessTokenUrl, String.class);
        System.out.println("checkAccessToken====accessTokenResult1:====" + accessTokenResult);
        JSONObject jsonObject = JSON.parseObject(accessTokenResult, JSONObject.class);
        if (jsonObject != null) {
            return jsonObject.containsKey("errcode") && "0".equals(jsonObject.getString("errcode"));
        }
        throw new RRException("请求异常", HttpStatus.ERROR);
    }

    /**
     * 微信 公众号 网页开发
     * 根据refreshToken 刷新accessToken
     */
    private String refreshAccessToken(String refreshToken, String appId) {
        if (StringUtils.isBlank(refreshToken) || StringUtils.isBlank(appId)) {
            throw new RRException("参数异常", HttpStatus.ERROR);
        }
        RestTemplate restTemplate = new RestTemplate();
        String accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + appId + "&grant_type=refresh_token&refresh_token=" + refreshToken;
        String accessTokenResult = restTemplate.getForObject(accessTokenUrl, String.class);
        System.out.println("refreshAccessToken===accessTokenResult2:====" + accessTokenResult);
        JSONObject jsonObject = JSON.parseObject(accessTokenResult, JSONObject.class);
        if (jsonObject != null) {
            if (jsonObject.containsKey("errcode")) {
                throw new RRException(jsonObject.get("errmsg").toString(), HttpStatus.ERROR);
            } else {
                return jsonObject.get("access_token").toString();
            }
        } else {
            throw new RRException("请求异常", HttpStatus.ERROR);
        }
    }

    /**
     * 通过accessToken openid 获取用户信息
     */
    public Map<String, Object> getUserInfo(String accessToken, String openid) {
        if (StringUtils.isBlank(accessToken) || StringUtils.isBlank(openid)) {
            throw new RRException("参数异常");
        }

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
        String userInfoResult = restTemplate.getForObject(userInfoUrl, String.class);
        System.out.println("微信用户信息:====" + userInfoResult);
        JSONObject jsonObject = JSON.parseObject(userInfoResult, JSONObject.class);
        if (jsonObject != null) {
            if (!jsonObject.containsKey("errcode")) {
                Map<String, Object> map = new HashMap<>(16);
                map.put("openid", jsonObject.get("openid").toString());
                map.put("nickname", jsonObject.get("nickname").toString());
                map.put("headimgurl", jsonObject.get("headimgurl").toString());
                map.put("sex", jsonObject.get("sex").toString());
                map.put("province", jsonObject.get("province").toString());
                map.put("city", jsonObject.get("city").toString());
                map.put("country", jsonObject.get("country").toString());
                return map;
            } else {
                throw new RRException(jsonObject.get("errmsg").toString(), HttpStatus.ERROR);
            }
        } else {
            throw new RRException("请求失败", HttpStatus.ERROR);
        }
    }

    /**
     * 小程序支付-统一下单 生成预支付订单
     * 参考文档：https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
     * @param openid     微信用户唯一标识
     * @param appid      微信分配的小程序ID
     * @param mchId      微信支付分配的商户号   微信用户唯一标识
     * @param outTradeNo 商家订单号
     * @param body       商品描述
     * @param attach     自定义参数
     * @param notifyUrl  回调地址
     * @param totalFee   订单总金额，单位为分 不能有小数点
     * @param key        key为商户平台设置的密钥key
     * @return
     */
    public static Map<String, String> miniProgramPay(String openid, String appid, String mchId, String outTradeNo, String totalFee, String key, String notifyUrl, String body, String attach, HttpServletRequest request) {
        // 生成微信预支付订单
        Map<String, String> map = new HashMap<>(16);
        map.put("openid", openid);
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("out_trade_no", outTradeNo);
        //随机字符串 String(32)
        map.put("nonce_str", WXPayUtil.generateNonceStr());
        //商品描述 String(128)
        map.put("body", body);
        map.put("fee_type", "CNY");
        map.put("sign_type", WXPayConstants.SignType.MD5.toString());
        //自定义参数
        map.put("attach", attach);
        ///回调地址
        map.put("notify_url", notifyUrl);
        map.put("trade_type", "JSAPI");
        map.put("total_fee", totalFee);
        //支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
        map.put("spbill_create_ip", request.getRemoteAddr());

        System.out.println("生成微信预支付订单==参数==" + map);
        RestTemplate restTemplate = new RestTemplate();
        try {
            //生成带有 sign 的 XML 格式字符串
            String requestXml = WXPayUtil.generateSignedXml(map, key, WXPayConstants.SignType.MD5);
            System.out.println("生成带有 sign 的 XML 格式字符串==" + requestXml);
            //调用 统一下单 接口
            // 设置restemplate编码为utf-8
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            String resultXml = restTemplate.postForObject("https://api.mch.weixin.qq.com/pay/unifiedorder", requestXml, String.class);
            System.out.println("生成微信预支付订单==结果resultXml==" + resultXml);
            //解析 xml结果转map
            Map<String, String> resultMap = WXPayUtil.xmlToMap(resultXml);
            System.out.println("生成微信预支付订单==结果resultMap==" + resultMap);
            //返给前端参数
            Map<String, String> result = new HashMap<>(16);
            result.put("appId", resultMap.get("appid"));
            //resultMap.get("nonce_str")
            result.put("nonceStr", WXPayUtil.generateNonceStr());
            result.put("package", "prepay_id=" + resultMap.get("prepay_id"));
            result.put("signType", WXPayConstants.SignType.MD5.toString());
            result.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));
            String sign = WXPayUtil.generateSignature(result, key, WXPayConstants.SignType.MD5);
            result.put("paySign", sign);
            //result.put("mchId", resultMap.get("mch_id"));
            //result.put("sign", resultMap.get("sign"));
            //result.put("tradeType", resultMap.get("trade_type"));
            //result.put("prepayId", resultMap.get("prepay_id"));
            System.out.println("生成微信预支付订单==返给前端参数==" + result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RRException(e.getMessage());
        }
    }

    /**
     * APP微信支付-统一下单 生成预支付订单
     * 参考文档：https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
     * @param appid      微信分配的小程序ID
     * @param mchId      微信支付分配的商户号   微信用户唯一标识
     * @param outTradeNo 商家订单号
     * @param body       商品描述
     * @param attach     自定义参数
     * @param notifyUrl  回调地址
     * @param totalFee   订单总金额，单位为分 不能有小数点
     * @param key        key为商户平台设置的密钥key
     * @return
     */
    public static Map<String, String> APPPay(String appid, String mchId, String outTradeNo, String totalFee, String key, String notifyUrl, String body, String attach, HttpServletRequest request) {
        // 生成微信预支付订单
        Map<String, String> map = new HashMap<>(16);
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("out_trade_no", outTradeNo);
        //随机字符串 String(32)
        map.put("nonce_str", WXPayUtil.generateNonceStr());
        //商品描述 String(128)
        map.put("body", body);
        map.put("fee_type", "CNY");
        map.put("sign_type", WXPayConstants.SignType.MD5.toString());
        //自定义参数
        map.put("attach", attach);
        ///回调地址
        map.put("notify_url", notifyUrl);
        //交易类型
        map.put("trade_type", "APP");
        map.put("total_fee", totalFee);
        //支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
        map.put("spbill_create_ip", request.getRemoteAddr());

        System.out.println("生成微信预支付订单==参数==" + map);
        RestTemplate restTemplate = new RestTemplate();
        try {
            //生成带有 sign 的 XML 格式字符串
            String requestXml = WXPayUtil.generateSignedXml(map, key, WXPayConstants.SignType.MD5);
            System.out.println("生成带有 sign 的 XML 格式字符串==" + requestXml);
            //调用 统一下单 接口
            // 设置restemplate编码为utf-8
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            String resultXml = restTemplate.postForObject("https://api.mch.weixin.qq.com/pay/unifiedorder", requestXml, String.class);
            System.out.println("生成微信预支付订单==结果resultXml==" + resultXml);
            //解析 xml结果转map
            Map<String, String> resultMap = WXPayUtil.xmlToMap(resultXml);
            System.out.println("生成微信预支付订单==结果resultMap==" + resultMap);
            //返给前端参数
            Map<String, String> result = new HashMap<>(16);
            //应用APPID
            result.put("appid", resultMap.get("appid"));
            //时间戳
            result.put("timestamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));
            //商户号
            result.put("partnerid", resultMap.get("mch_id"));
            //预支付交易会话标识
            result.put("prepayid", resultMap.get("prepay_id"));
            //随机字符串	 WXPayUtil.generateNonceStr()
            result.put("noncestr", resultMap.get("nonce_str") );
            //扩展字段
            result.put("package", "Sign=WXPay");
            //签名 resultMap.get("sign")
            String sign = WXPayUtil.generateSignature(result, key, WXPayConstants.SignType.MD5);
            result.put("sign", sign);
            //交易类型
            result.put("tradeType", resultMap.get("trade_type"));
            System.out.println("生成微信预支付订单==返给前端参数==" + result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RRException(e.getMessage());
        }
    }

    /**
     * 小程序获取用户信息(获取手机号)
     *
     * @param encryptedData
     * @param iv
     * @param sessionKey
     * @return <p>
     * phoneNumber    String	用户绑定的手机号（国外手机号会有区号）
     * purePhoneNumber	String	没有区号的手机号
     * countryCode	String	区号
     * </p>
     * @throws Exception
     */
    public static Map<String, Object> getMiniProgramUserPhone(String encryptedData, String iv, String sessionKey) throws Exception {
        // 被加密的数据
        byte[] dataByte = Base64.getDecoder().decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.getDecoder().decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.getDecoder().decode(iv);
        // 如果密钥不足16位，那么就补足
        int base = 16;
        if (keyByte.length % base != 0) {
            int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
            byte[] temp = new byte[groups * base];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
            keyByte = temp;
        }
        // 初始化
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
        SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
        parameters.init(new IvParameterSpec(ivByte));
        cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
        try {
            byte[] resultByte = cipher.doFinal(dataByte);
            String result = new String(resultByte, "UTF-8");
            System.out.println("result:" + result);
            return JSONObject.parseObject(result, Map.class);
        } catch (BadPaddingException e) {
            throw new RRException("解析失败");
        }
    }

}

